﻿using System;
using System.CodeDom.Compiler;
using EnvDTE;
using System.CodeDom;
using System.Reflection;
using System.Runtime.InteropServices;
using VSLangProj;
using Microsoft.VisualStudio.Designer.Interfaces;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using IServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;

namespace Contoso.VisualStudio
{
    public abstract class VsSingleFileGeneratorWithSite : VsSingleFileGenerator, IObjectWithSite
    {
        private static Guid CodeDomInterfaceGuid = new Guid("{73E59688-C7C4-4a85-AF64-A538754784C5}");
        private object _site;
        private CodeDomProvider _codeDomProvider;
        private ServiceProvider _serviceProvider;

        protected VsSingleFileGeneratorWithSite() { }

        protected void AddReferenceDLLToProject(string[] referenceDLL)
        {
            if (referenceDLL.Length != 0)
            {
                var service = GetService(typeof(ProjectItem));
                if (service == null)
                    Error(0, "Unable to add DLL to project references: {0}.  Please Add them manually.", GetDLLNames(referenceDLL));
                else
                {
                    var project = ((ProjectItem)service).ContainingProject;
                    if (project == null)
                        Error(0, "Unable to add DLL to project references: {0}.  Please Add them manually.", GetDLLNames(referenceDLL));
                    else
                    {
                        var project2 = (project.Object as VSProject);
                        if (project2 == null)
                            Error(0, "Unable to add DLL to project references: {0}.  Please Add them manually.", GetDLLNames(referenceDLL));
                        else
                            try
                            {
                                for (int i = 0; i < referenceDLL.Length; i++)
                                    project2.References.Add(referenceDLL[i]);
                            }
                            catch (Exception) { Error(0, "Unable to add DLL to project references: {0}.  Please Add them manually.", GetDLLNames(referenceDLL)); }
                    }
                }
            }
        }

        private static string GetDLLNames(string[] DLLToAdd)
        {
            if (DLLToAdd == null || DLLToAdd.Length == 0)
                return string.Empty;
            var str = DLLToAdd[0];
            for (int i = 1; i < DLLToAdd.Length; i++)
                str = str + ", " + DLLToAdd[i];
            return str;
        }

        //protected virtual void GenerateVersionComment(System.CodeDom.CodeNamespace codeNamespace)
        //{
        //    codeNamespace.Comments.Add(new CodeCommentStatement(string.Empty));
        //    codeNamespace.Comments.Add(new CodeCommentStatement(string.Format("This source code was auto-generated by {0}, Version {1}.", Assembly.GetExecutingAssembly().GetName().Name, Environment.Version.ToString())));
        //    codeNamespace.Comments.Add(new CodeCommentStatement(string.Empty));
        //}

        //protected virtual ICodeGenerator GetCodeWriter()
        //{
        //    var codeProvider = CodeProvider;
        //    return (codeProvider != null ? codeProvider.CreateGenerator() : null);
        //}

        public override string GetDefaultExtension()
        {
            var fileExtension = CodeProvider.FileExtension;
            if (!string.IsNullOrEmpty(fileExtension) && fileExtension[0] != '.')
                fileExtension = "." + fileExtension;
            return fileExtension;
        }

        #region Site

        protected object GetService(Guid serviceGuid) { return SiteServiceProvider.GetService(serviceGuid); }
        protected object GetService(Type serviceType) { return SiteServiceProvider.GetService(serviceType); }

        public virtual void GetSite(ref Guid riid, out IntPtr ppvSite)
        {
            if (_site == null)
                throw new COMException("object is not sited", -2147467259);
            var siteAsIUnknown = Marshal.GetIUnknownForObject(_site);
            try
            {
                Marshal.QueryInterface(siteAsIUnknown, ref riid, out ppvSite);
                if (ppvSite == IntPtr.Zero)
                    throw new COMException("object is not interface", -2147467262);
            }
            finally
            {
                if (siteAsIUnknown != IntPtr.Zero)
                {
                    Marshal.Release(siteAsIUnknown);
                    siteAsIUnknown = IntPtr.Zero;
                }
            }
        }

        public virtual void SetSite(object pUnkSite)
        {
            _site = pUnkSite;
            _codeDomProvider = null;
            _serviceProvider = null;
        }

        protected virtual CodeDomProvider CodeProvider
        {
            get
            {
                if (_codeDomProvider == null)
                {
                    var service = (IVSMDCodeDomProvider)GetService(CodeDomInterfaceGuid);
                    if (service != null)
                        _codeDomProvider = (CodeDomProvider)service.CodeDomProvider;
                }
                return _codeDomProvider;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();
                _codeDomProvider = value;
            }
        }

        private ServiceProvider SiteServiceProvider
        {
            get
            {
                if (_serviceProvider == null)
                {
                    var site = (_site as IServiceProvider);
                    _serviceProvider = new ServiceProvider(site);
                }
                return _serviceProvider;
            }
        }

        #endregion
    }
}
